using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Rifts.Models.Characters;
using Granite.Collections;

namespace Rifts.Models.Definitions
{
	public class CharacterClass
	{
		private readonly Catalog m_Catalog;
		readonly XElement m_Book;
		readonly XElement m_ClassDefinition;
		public CharacterClass(XElement book, XElement classDefinition, Catalog catalog)
		{
			m_ClassDefinition = classDefinition;
			m_Book = book;
			m_Catalog = catalog;
		}

		public string ClassReference
		{
			get
			{
				if (m_ClassDefinition.HasAttribute("Page"))
					return m_Book.GetAttribute("Name") + " page " + m_ClassDefinition.GetAttribute("Page");
				else
					return m_Book.GetAttribute("Name");
			}
		}

		public ISet<string> AllowedRaces
		{
			get
			{
				var raceList = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);

				var raceNode = m_ClassDefinition.Element("Races");
				if (raceNode != null)
				{
					foreach (var race in raceNode.Elements("Race"))
					{
						raceList.Add(race.GetAttribute("Name"));
					}
				}

				return raceList;
			}
		}

		public string Name
		{
			get { return m_ClassDefinition.Attribute("Name").Value; }
		}

		public bool IsAllowed(Character character)
		{
			var requiresNode = m_ClassDefinition.Element("Requires");
			if (requiresNode != null)
			{
				foreach (var item in requiresNode.Elements())
				{
					if (item.Name == "Attribute")
					{
						var minValue = int.Parse(item.GetAttribute("MinValue"));
						if ((character.GetAttribute(item.GetAttribute("Name")) ?? 0) < minValue)
							return false;
					}
				}
			}

			return true;
		}

		public void BuildSteps(IList<BuildStep> stepList)
		{

			//TODO: Do you really need each step? Check the XML.

			stepList.Add(new BuildStep("ClassSkills"));
			stepList.Add(new BuildStep("RelatedSkills"));
			stepList.Add(new BuildStep("SecondarySkills"));

			//TODO: Calculate more steps from the class features

		}

		public void Apply(Random random, Character character)
		{
			character.Class = Name;
			character.ClassReference = ClassReference;

			//TODO: Apply standard bonuses
		}

		public IList<SkillDefinitionViewModel> OccSkills()
		{
			var result = new List<SkillDefinitionViewModel>();

			foreach (var skill in m_ClassDefinition.Element("Skills").Element("Class").Elements("Skill"))
			{
				var occBonus = skill.GetInt2("Add");
				var value = skill.GetInt2("Value");
				var perLevel = skill.GetInt2("PerLevel");
				var catalogEntry = (from s in m_Catalog.Skills where s.Name == skill.GetAttribute("Name") select s).FirstOrDefault();
				if (catalogEntry == null)
					throw new Exception("The skill '" + skill.GetAttribute("Name") + "' could not be found.");

				result.Add(new SkillDefinitionViewModel(SkillType.Occ, catalogEntry, occBonus, value, perLevel, 1));
			}

			return result;
		}

		List<SkillDefinitionViewModelCollection> ProcessSkillBlock(XElement skillBlock, bool isSecondary)
		{
			var skillType = isSecondary ? SkillType.Secondary : SkillType.OccRelated;

			var result = new List<SkillDefinitionViewModelCollection>();
			foreach (var skillGroup in skillBlock.Elements("SkillGroup"))
			{
				var groupList = new SkillDefinitionViewModelCollection();
				groupList.Name = skillGroup.GetAttribute("Name");
				var groupBonus = skillGroup.GetInt2("Add");

				var allowAll = skillGroup.GetBool("AllowAll", false);

				foreach (var skill in skillGroup.Elements("Allow"))
				{
					var occBonus = skill.GetInt2("Add") ?? groupBonus;
					var value = skill.GetInt2("Value");
					var perLevel = skill.GetInt2("PerLevel");
					var cost = skill.GetInt2("Cost");
					var catalogEntries = m_Catalog.Skills.FindAllByName(skill.GetAttribute("Name")); 						
					if (catalogEntries.Count==0)
						throw new Exception("The skill '" + skill.GetAttribute("Name") + "' could not be found in skill group '" + groupList.Name + "'. It is needed for " + (isSecondary ? "secondary skills" : Name + " related skills."));

					foreach(var catalogEntry in catalogEntries)
						groupList.Add(new SkillDefinitionViewModel(skillType, catalogEntry, occBonus, value, perLevel, cost));
				}

				if (allowAll) //add any skill not explicitly added above
				{
					foreach (var catalogEntry in m_Catalog.Skills.Where(s => s.GroupName == groupList.Name))
					{

						if (!groupList.Any(s => s.Name == catalogEntry.Name))
							groupList.Add(new SkillDefinitionViewModel(skillType, catalogEntry, groupBonus, null, null, null));
					}
				}

				//Remove disallowed skills
				foreach (var skill in skillGroup.Elements("Disallow"))
				{
					var skillName = skill.GetAttribute("Name");
					groupList.RemoveAll(s => s.Name == skillName);
				}

				if (groupList.Count > 0)
					result.Add(groupList);
			}
			return result;
		}

		public List<SkillDefinitionViewModelCollection> RelatedSkills()
		{
			var skillBlock = m_ClassDefinition.Element("Skills").Element("Related");
			List<SkillDefinitionViewModelCollection> result = ProcessSkillBlock(skillBlock, false);
			return result;
		}

		public List<SkillDefinitionViewModelCollection> SecondarySkills()
		{
			var skillBlock = m_Catalog.SecondarySkillsDefinition;
			List<SkillDefinitionViewModelCollection> result = ProcessSkillBlock(skillBlock, true);
			return result;
		}



		public int RelatedSkillPicks
		{
			get { return m_ClassDefinition.Element("Skills").Element("Related").GetInt2("Count") ?? 0; }
		}

		public int SecondarySkillPicks
		{
			get { return m_ClassDefinition.Element("Skills").Element("Secondary").GetInt2("Count") ?? 0; }
		}

		public int OccSkillPicks
		{
			get
			{
				return m_ClassDefinition.Element("Skills").Element("Class").GetInt2("Count")
					?? m_ClassDefinition.Element("Skills").Element("Class").Elements("Skill").Count();
			}
		}

	}
}

